package indi.cloud.common.elasticsearch;

import indi.cloud.common.elasticsearch.core.esmethod.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
public class EsQueryParse {
    private EsQueryParse() {
    }

    public static <T> Query convert2Query(T t) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(t);
        queryBuilder.withQuery(boolQueryBuilder);
        return queryBuilder.build();
    }

    private static <T> BoolQueryBuilder getBoolQueryBuilder(T t) {
        return getBoolQueryBuilder(t, null);
    }

    private static <T> BoolQueryBuilder getBoolQueryBuilder(T t, String nestedPath) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Class<?> clazz = t.getClass();
        Field[] fields = clazz.getDeclaredFields();
        nestedPath = nestedPath == null ? "" : nestedPath;
        try {
            for (Field field : fields) {
                Object value = ClassUtils.getPublicMethod(clazz, "get" + captureName(field.getName())).invoke(t);
                if (value == null || value.equals("")) {
                    continue;
                }
                if (field.isAnnotationPresent(EsLike.class)) {
                    WildcardQueryBuilder query = getLikeQuery(field, value, nestedPath);
                    boolQueryBuilder.must(query);
                }
                if (field.isAnnotationPresent(EsEquals.class)) {
                    MatchQueryBuilder query = getEqualsQuery(field, value, nestedPath);
                    boolQueryBuilder.must(query);
                }
                if (field.isAnnotationPresent(EsRange.class)) {
                    RangeQueryBuilder query = getRangeQuery(field, value, nestedPath);
                    boolQueryBuilder.must(query);
                }
                if (field.isAnnotationPresent(EsIn.class)) {
                    TermsQueryBuilder query = getInQuery(field, (List<?>) value, nestedPath);
                    boolQueryBuilder.must(query);
                }
                if (field.isAnnotationPresent(EsNotNull.class)) {
                    ExistsQueryBuilder query = getNotNullQuery(field, nestedPath);
                    boolQueryBuilder.must(query);
                }
                if (field.isAnnotationPresent(EsNotNullFields.class)) {
                    List<ExistsQueryBuilder> query = getNotNullQuery((List<String>) value, nestedPath);
                    Optional.ofNullable(query).orElse(new ArrayList<>())
                            .forEach(boolQueryBuilder::must);
                }
                if (field.isAnnotationPresent(EsNested.class)) {
                    NestedQueryBuilder query = getNestedQuery(field, value);
                    boolQueryBuilder.must(query);
                }
            }
        } catch (Exception e) {
            log.info("ES查询解析异常：{}", e.getMessage());
        }
        return boolQueryBuilder;
    }

    private static TermsQueryBuilder getInQuery(Field field, List<?> value, String nestedPath) {
        EsIn esIn = field.getAnnotation(EsIn.class);
        String filedName = getFiledName(field, esIn.name(), nestedPath);
        return QueryBuilders.termsQuery(filedName, value);
    }

    private static RangeQueryBuilder getRangeQuery(Field field, Object value, String nestedPath) {
        EsRange esRange = field.getAnnotation(EsRange.class);
        String filedName = getFiledName(field, esRange.name(), nestedPath);
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(filedName)
                .includeLower(esRange.includeLower())
                .includeUpper(esRange.includeUpper());
        if (esRange.lt()) {
            rangeQueryBuilder.gt(value);
        }
        if (esRange.gt()) {
            rangeQueryBuilder.gt(value);
        }
        return rangeQueryBuilder;
    }

    private static MatchQueryBuilder getEqualsQuery(Field field, Object value, String nestedPath) {
        EsEquals esEquals = field.getAnnotation(EsEquals.class);
        String filedName = getFiledName(field, esEquals.name(), nestedPath);
        return QueryBuilders.matchQuery(filedName, value);
    }

    private static WildcardQueryBuilder getLikeQuery(Field field, Object value, String nestedPath) {
        String likeValue = (String) value;
        EsLike esLike = field.getAnnotation(EsLike.class);
        String filedName = getFiledName(field, esLike.name(), nestedPath);
        if (field.getType() == String.class) {
            filedName = filedName + ".keyword";
        }
        if (esLike.leftLike()) {
            likeValue = "*" + likeValue;
        }
        if (esLike.rightLike()) {
            likeValue = likeValue + "*";
        }
        if (esLike.whileLike()) {
            likeValue = "*" + likeValue + "*";
        }
        return QueryBuilders.wildcardQuery(filedName, likeValue);
    }

    private static ExistsQueryBuilder getNotNullQuery(Field field, String nestedPath) {
        EsNotNull esNotNull = field.getAnnotation(EsNotNull.class);
        String filedName = getFiledName(field, esNotNull.name(), nestedPath);
        return QueryBuilders.existsQuery(filedName);
    }

    private static List<ExistsQueryBuilder> getNotNullQuery(List<String> value, String nestedPath) {
        if (CollectionUtils.isEmpty(value)) {
            return new ArrayList<>();
        }
        return value.stream()
                .map(item -> getFiledName(item, nestedPath))
                .map(QueryBuilders::existsQuery)
                .collect(Collectors.toList());
    }

    private static NestedQueryBuilder getNestedQuery(Field field, Object object) {
        EsNested esNested = field.getAnnotation(EsNested.class);
        String nestedPath = getFiledName(field, esNested.name(), "");
        QueryBuilder boolQueryBuilder = getBoolQueryBuilder(object, nestedPath);
        return QueryBuilders.nestedQuery(nestedPath, boolQueryBuilder, ScoreMode.None);
    }

    private static String getFiledName(Field field, String name, String nestedPath) {
        String fileName = name;
        if (field != null) {
            fileName = StringUtils.isBlank(name) ? field.getName() : name;
        }
        if (StringUtils.isBlank(nestedPath)) {
            return fileName;
        }
        return nestedPath + "." + fileName;
    }

    private static String getFiledName(String name, String nestedPath) {
        return getFiledName(null, name, nestedPath);
    }

    public static String captureName(String name) {
        char[] cs = name.toCharArray();
        cs[0] -= 32;
        return String.valueOf(cs);
    }
}
